#!/usr/bin/env python
# Project home: https://code.google.com/p/shuffle-music/
# Created by: alexds9

import sys
import os
import random
from lxml import etree
import shutil
import re
import zipfile

def enforce_path_ending_with_separator(path):
    if path != "":
        separator_symbol = os.path.join("a","")[1]

        #print "path: " + path
        if path[len(path) - 1] != separator_symbol:
            return path + separator_symbol
        else:
            return path
    else:
        return ""

def proccess_arguments():
    arguments   = sys.argv[1:]
    dir_list    = []
    
    #j = 0
    conf_shuffle    = False
    conf_unshuffle  = False
    conf_reshuffle  = False
    conf_help       = False
    
    for argument in arguments:
        if      ("-s"           == argument) or ("--shuffle"    == argument):
            conf_shuffle    = True
        elif    ("-u"           == argument) or ("--unshuffle"  == argument):
            conf_unshuffle  = True
        elif    ("-r"           == argument) or ("--reshuffle"  == argument):
            conf_reshuffle  = True
        elif    ("-h"           == argument) or ("--help"       == argument):
            conf_help       = True
        else:
            dir_list.append(enforce_path_ending_with_separator(argument))
            
        #print( "sys.argv[" + str(j) + "] = " + argument )
        #j += 1
    return conf_shuffle, conf_unshuffle, conf_reshuffle, conf_help, dir_list


def shuffle_list(list_of_all_files):
    random.shuffle(list_of_all_files)
    
    name_index = 0
    for file_data in list_of_all_files:
        file_data["shuffled_name"] = file_name(name_index, file_data["unshuffled_name"] )
        name_index += 1

def create_shuffle_list(directory_to_change):
    list_of_all_directories = []
    list_of_all_files		= []
    directory_to_change_len = len(directory_to_change)
    # shuffle_00000.xml
    files_and_directories_to_skip = re.compile( r"(shuffle_[0-9][0-9][0-9][0-9][0-9]\.zip|shuffle\.py|\..*|~.*)" )

    #print(directories)
    for dirname, dirnames, filenames in os.walk(directory_to_change):
        
        if files_and_directories_to_skip.search(dirname):
            continue
        
        relative_path = enforce_path_ending_with_separator(dirname[directory_to_change_len:])
        #print "relative_path: " + relative_path
        if "" != relative_path:
            #print "\"\" != relative_path: " + relative_path
            list_of_all_directories.append(relative_path)

        # Remove all configuration files
        filenames = [filename for filename in filenames if not files_and_directories_to_skip.match(filename)]
        
        for filename in filenames:
            #print( "path: " + os.path.join(dirname, filename) )
            file_data = {}
            file_data["unshuffled_name"] = filename
            file_data["shuffled_name"]  = ""
            file_data["unshuffled_path"] = relative_path
            list_of_all_files.append( file_data )
        
    shuffle_list(list_of_all_files)

    
    return list_of_all_directories, list_of_all_files
    
    
def file_name(index, old_name):
    return str(index).zfill(5) + "_" + old_name

def configuration_file_name(index):
    return "shuffle_" + str(index).zfill(5) + ".xml"

def configuration_file_archive_name(index):
    return "shuffle_" + str(index).zfill(5) + ".zip"

def configuration_file_get_new_name(path):
    i = 0
    
    while os.path.exists(path + configuration_file_archive_name(i)):
        i += 1
    
    return i

def configuration_file_deactivate( path, configuration_file_name_index ):
    
    #print path
    file_descripter = zipfile.ZipFile( path, mode='r' )
    #print configuration_file_name(configuration_file_name_index)
    configuration_file = file_descripter.read( configuration_file_name(configuration_file_name_index) )
    #print configuration_file
    xml_configuration_root = etree.fromstring(configuration_file)
    
    xml_active = xml_get_unique_attribute( xml_configuration_root, "active" )
    #print xml_active.text.strip()
    active_mode_value = xml_active.text.strip()
    if active_mode_value == "True":
        active_status   = True
        xml_active.text = "False"
        #print etree.tostring(xml_configuration_root, pretty_print=True)
        file_descripter.close()
        file_descripter = zipfile.ZipFile( path, mode='w', compression=zipfile.ZIP_DEFLATED )
        file_descripter.writestr( configuration_file_name(configuration_file_name_index), etree.tostring(xml_configuration_root, pretty_print=True))
        
    elif active_mode_value == "False":
        active_status = False
        
    else:
        raise NameError("Unknown value")
    
    
    file_descripter.close()
    
    return active_status

def configuration_file_find_active_name_index_and_deactivate( directory ):
    #i           = 0
    file_found  = False
    found_index = -1
    #while False == file_exist:
    #    path        = directory + configuration_file_archive_name(i)
    #    file_exist  = os.path.exists( path, configuration_file_archive_name(i) )
    #    i += 1
    #i -= 1;
    
    configuration_name_format = re.compile( r"shuffle_(\d{5,5})+.zip" )
    
    for file_name in os.listdir(directory):
        re_match = re.match( configuration_name_format, file_name )
        if re_match:
            #print "match: " + file_name
            configuration_file_name_index = int(re_match.group(1))
            if ( True == configuration_file_deactivate( os.path.join( directory, file_name ), configuration_file_name_index ) ):
                if file_found == False:
                    found_index = configuration_file_name_index
            #print re.group(0)
        #else:
        #    print "not match: " + file_name
    
    return found_index

def create_configuration_file( directory_to_change, all_directories, shuffled_files_data_list):
    
    root = etree.Element("configurations")
    
    xml_active_child = etree.Element("active")
    xml_active_child.text = "True"
    root.append(xml_active_child)
    
    configurations_child_for_directories = etree.Element("directories")
    
    for directory in all_directories:
        xml_directories_child = etree.Element("directory")
        xml_directories_child.text = directory
        configurations_child_for_directories.append(xml_directories_child)
    root.append(configurations_child_for_directories)

    configurations_child_for_files = etree.Element("files")
    for shuffled_data_file in shuffled_files_data_list:
        #print(shuffled_file)
        xml_file_child = etree.Element("file")
        
        xml_file_unshuffled_name = etree.Element("unshuffled_name")
        xml_file_unshuffled_name.text = shuffled_data_file["unshuffled_name"]
        xml_file_child.append(xml_file_unshuffled_name)
        
        xml_file_shuffled_name = etree.Element("shuffled_name")
        xml_file_shuffled_name.text =  shuffled_data_file["shuffled_name"]
        xml_file_child.append(xml_file_shuffled_name)
        
        xml_file_unshuffled_path = etree.Element("unshuffled_path")
        xml_file_unshuffled_path.text =  shuffled_data_file["unshuffled_path"]
        xml_file_child.append(xml_file_unshuffled_path)
        
        configurations_child_for_files.append(xml_file_child)

    root.append(configurations_child_for_files)

    # pretty string
    xml = etree.tostring(root, pretty_print=True)
    #print xml
    
    configuration_file_find_active_name_index_and_deactivate( directory_to_change )
    configuration_file_unused_name_index = configuration_file_get_new_name(directory_to_change)
    
    file_descripter = zipfile.ZipFile( os.path.join( directory_to_change, configuration_file_archive_name( configuration_file_unused_name_index ) ), mode='w', compression=zipfile.ZIP_DEFLATED )
    file_descripter.writestr( configuration_file_name(configuration_file_unused_name_index), xml)
    file_descripter.close()
    
        
def xml_get_unique_attribute(node, attribute_name):
    children = node.findall(attribute_name)

    if len(children) != 1:
        for child in children:
            print child
        raise NameError( "WrongXMLStructure" )
    else:
        return children[0]

def import_configuration_file( directory_to_change, configuration_file_index ):
    found_configuration         = False
    list_of_all_directories     = []
    shuffled_files_data_list    = []
    
    file_descripter = zipfile.ZipFile( os.path.join( directory_to_change, configuration_file_archive_name( configuration_file_index ) ), "r")
    configuration_file = file_descripter.read( configuration_file_name( configuration_file_index ) )
    file_descripter.close()
    
    found_configuration = True # TODO: make an actual check that file exist in some efficent way
    
    xml_configuration_root = etree.fromstring(configuration_file)
    
    #xml_active = xml_get_unique_attribute( xml_configuration_root, "active" )
    
    xml_directories = xml_get_unique_attribute( xml_configuration_root, "directories" )
    
    for xml_directory in xml_directories:
        #print xml_directory.text
        list_of_all_directories.append( xml_directory.text )
        
        
    xml_files = xml_get_unique_attribute( xml_configuration_root, "files" )
    for xml_file in xml_files:
        file_data = {}
        file_data["unshuffled_name"] = xml_get_unique_attribute( xml_file, "unshuffled_name" ).text
        #print "unshuffled_name: " + file_data["unshuffled_name"]
        file_data["shuffled_name"]   = xml_get_unique_attribute( xml_file, "shuffled_name" ).text
        #print "shuffled_name: " + file_data["shuffled_name"]
        file_data["unshuffled_path"] = xml_get_unique_attribute( xml_file, "unshuffled_path" ).text
        #print "unshuffled_path: " + file_data["unshuffled_path"]
        shuffled_files_data_list.append(file_data)
        
    return found_configuration, list_of_all_directories, shuffled_files_data_list
    


def create_folders( list_of_folders ):
    
    for folder in list_of_folders:
        if not os.path.exists( folder ):
            os.mkdir( folder )
            #print "os.mkdir( " + folder + " )"

def delete_folders( list_of_folders ):
    
    for folder in list_of_folders:
        if os.path.exists( folder ):
            shutil.rmtree( folder )
            #print "shutil.rmtree( " + folder + " )"

def move_files( list_of_sources, list_of_destinations ):
    amount_of_files = len(list_of_sources)
    
    assert amount_of_files == len(list_of_destinations), "Wrong number of elements in one of the list"
    
    for file_number in range( 0, amount_of_files ):
        source_file = list_of_sources[file_number]
        
        if os.path.exists( source_file ):
            destination_file = list_of_destinations[file_number]
            shutil.move( source_file, destination_file )
            #print "shutil.move( " + source_file + ", " + destination_file + " )"
        

def restore_directories_and_files_to_unshuffled_places( directory_to_change, list_of_all_directories, shuffled_files_data_list ):
    full_directories = [ directory_to_change + directory for directory in list_of_all_directories ]
    #print full_directories
    create_folders(full_directories)
    
    full_unshuffled_list = [ directory_to_change + file_data["unshuffled_path"] + file_data["unshuffled_name"] for file_data in shuffled_files_data_list ]
    #print full_unshuffled_list
    full_shuffled_list   = [ directory_to_change + file_data["shuffled_name"] for file_data in shuffled_files_data_list ]
    #print full_shuffled_list
    move_files( full_shuffled_list, full_unshuffled_list )


def move_files_and_delete( directory_to_change, list_of_all_directories, shuffled_files_data_list):
    
    full_unshuffled_list = [ directory_to_change + file_data["unshuffled_path"] + file_data["unshuffled_name"] for file_data in shuffled_files_data_list ]
    #print full_unshuffled_list
    full_shuffled_list   = [ directory_to_change + file_data["shuffled_name"] for file_data in shuffled_files_data_list ]
    #print full_shuffled_list
    move_files( full_unshuffled_list, full_shuffled_list )
    
    full_directories_list = [ directory_to_change + directory for directory in list_of_all_directories ]
    #print full_directories_list
    delete_folders( full_directories_list )
            
def reshuffle_the_files( directory_to_change, shuffled_files_data_list ):
    reshuffled_files_data_list = [ { "unshuffled_name": file_data["unshuffled_name"], "shuffled_name": "", "shuffled_old_name": file_data["shuffled_name"], "unshuffled_path": file_data["unshuffled_path"] } for file_data in shuffled_files_data_list ]
    #print reshuffled_files_data_list
    #print "\n\n\n\n"
    
    shuffle_list(reshuffled_files_data_list)
    #print reshuffled_files_data_list
    #print "\n\n\n\n"
    
    full_old_shuffled_list  = [ directory_to_change + file_data["shuffled_old_name"] for file_data in reshuffled_files_data_list ]
    #print full_old_shuffled_list
    #print "\n\n\n\n"
    full_new_shuffled_list  = [ directory_to_change + file_data["shuffled_name"] for file_data in reshuffled_files_data_list ]
    #print full_new_shuffled_list
    #print "\n\n\n\n"
    
    for file_data in reshuffled_files_data_list:
        file_data.pop("shuffled_old_name")
        
    #print reshuffled_files_data_list
    #print "\n\n\n\n"
    move_files( full_old_shuffled_list, full_new_shuffled_list )
    
    return reshuffled_files_data_list
    
def main():
    random.seed()
    
    conf_shuffle, conf_unshuffle, conf_reshuffle, conf_help, directories_to_change = proccess_arguments()
    
    if conf_shuffle:
        directory_to_change = directories_to_change[0]
        list_of_all_directories, shuffled_files_data_list = create_shuffle_list(directory_to_change)
        create_configuration_file(directory_to_change, list_of_all_directories, shuffled_files_data_list)
        move_files_and_delete(directory_to_change, list_of_all_directories, shuffled_files_data_list)
        
    elif conf_unshuffle:
        directory_to_change = directories_to_change[0]
        configuration_file_last_name_index = configuration_file_find_active_name_index_and_deactivate( directory_to_change )
        found_configuration, list_of_all_directories, shuffled_files_data_list = import_configuration_file( directory_to_change, configuration_file_last_name_index )

        if not found_configuration :
            print "Couldn't find configuration file."
        else:
            restore_directories_and_files_to_unshuffled_places( directory_to_change, list_of_all_directories, shuffled_files_data_list )
         
    elif conf_reshuffle:
        directory_to_change = directories_to_change[0]
        configuration_file_find_name_index = configuration_file_find_active_name_index_and_deactivate( directory_to_change ) 
        found_configuration, list_of_all_directories, shuffled_files_data_list = import_configuration_file( directory_to_change, configuration_file_find_name_index )
        
        reshuffled_files_data_list = reshuffle_the_files( directory_to_change, shuffled_files_data_list )
        create_configuration_file(directory_to_change, list_of_all_directories, reshuffled_files_data_list)
        
    elif conf_help:
        print("""NAME
       shuffle.py

SYNOPSIS
       shuffle.py [-s DIRECTORY | --shuffle DIRECTORY | -u DIRECTORY | --unshuffle DIRECTORY | -r DIRECTORY | --reshuffle DIRECTORY | -h | --help ]

DESCRIPTION
       Shuffle script for simple MP3 players that can't shuffle files by themselves.
       
       This script takes given directory, copy all the files to base directory, and rename them in random order, what's we refer as "shuffle". This script may also reshuffle the directory in new order, or unshuffle it to original names and directory structures. Each shuffle create configuration file with all the moved files, it's saved in zip file, it shouldn't be deleted if you want to unshuffle the directory.
       
       Home page: https://code.google.com/p/shuffle-music/

OPTIONS
       -s, --shuffle
              Shuffle given DIRECTORY. All the files in DIRECTORY renamed in random order, and moved into DIRECTORY, all subdirectories deleted.

       -u, --unshuffle
              Unshuffle given DIRECTORY. All shuffled files renamed to their original names, and moved to thier original subdirectories.

       -r, --reshuffle
              Reshuffle given DIRECTORY. The order of the shuffled files changed and thier names changed accordingly.

       -h, --help
              Print this help message.

COPYRIGHT
       GNU GPL v3
       
NOTES
       Your ideas, suggestions and bug reports are welcome in: https://code.google.com/p/shuffle-music/issues/list
""")
               

main()

